<template>
  <div>
    <n-spin :show="resourceLoading">

      <n-alert type="error" title="StableDiffusion无法访问" v-if="!isResourceOk" style="height: 80px">
        {{paintArtEnums.stableDiffusion_not_online}}
      </n-alert>

      <n-form v-else inline :model="storyPaintConfig" ref="formRef1" :rules="rules">
        <n-grid :x-gap="10">
          <n-grid-item span="8">
            <n-form-item label="CHECKPOINT" path="defaultModel">
              <n-select
                  v-model:value="storyPaintConfig.defaultModel"
                  :options="modelOption"
                  @update:value="changeModel"
                  style="width: 100%"
              />
            </n-form-item>
          </n-grid-item>
          <n-grid-item :span="8">
            <n-form-item label="VAE"  >
              <n-select
                  :options="vaeOption"
                  v-model:value="storyPaintConfig.defaultVae"
                  @update:value="changeVae"
                  clearable
              />
            </n-form-item>
          </n-grid-item>
          <n-grid-item span="8">
            <n-form-item :label="'CLIP终止层数{'+ storyPaintConfig.defaultClipSkip+'}'"  >
              <n-slider v-model:value="storyPaintConfig.defaultClipSkip" step="1" :min="1" :max="12"/>
            </n-form-item>
          </n-grid-item>
        </n-grid>
      </n-form>
    </n-spin>
    <n-divider style="margin-top: 0;margin-bottom: 14px;" title-placement="left">
      参数设置
    </n-divider>
    <n-grid :x-gap="10">
      <n-grid-item span="8">
        <n-form :show-label="false" :model="storyPaintConfig" :rules="rules" ref="formRef2">
          <n-form-item label="测试提示词" path="prompt">
            <n-input v-model:value="demoPrompt" type="textarea" placeholder="请输入测试提示词"/>
          </n-form-item>
          <n-form-item label="预设正向提示词" path="defaultPrompt">
            <n-input v-model:value="storyPaintConfig.defaultPrompt" type="textarea" placeholder="请输入预设正向提示词"/>
          </n-form-item>
          <n-form-item label="预设反向提示词" path="defaultNegativePrompt">
            <n-input placeholder="请输入反向提示词" type="textarea"
                     v-model:value="storyPaintConfig.defaultNegativePrompt"/>
          </n-form-item>

          <n-form-item label="尺寸设置" path="sizeOption">
            <n-select
                :options="imageSizeOption"
                v-model:value="storyPaintConfig.sizeOption"
                @update:value="setSizeOption"
                clearable
            />
          </n-form-item>
          <n-form-item label="详细尺寸" path="width" v-if="storyPaintConfig.sizeOption==='6'">
            <n-input-number v-model:value="storyPaintConfig.defaultHeight" style="width: 45%; ">
              <template #prefix>
                高度
              </template>
            </n-input-number>
            <n-input-number v-model:value="storyPaintConfig.defaultWidth" style="width: 45%;margin-left: 10px;">
              <template #prefix>
                宽度
              </template>
            </n-input-number>
          </n-form-item>
        </n-form>
      </n-grid-item>
      <n-grid-item span="8">
        <n-el style="background: var(--hover-color);">
          <n-space justify="center">
            <div class="image-container">
              <n-image
                  class="centered-image"
                  :height="280"
                  :object-fit="'contain'"
                  :src="fileFunction.getViewFilePathByUserFileId(imageSrc)"
              />
            </div>
          </n-space>
        </n-el>
        <n-space style="margin-top:20px">
          <n-scrollbar x-scrollable style="max-height: 80px">
            <div style="white-space: nowrap;">
              <n-image v-for="pic in picList"
                       preview-disabled
                       :lazy="true"
                       :width="70"
                       size="small"
                       @click="imageSrc = pic"
                       :class="{ 'selected': pic == imageSrc }"
                       style="margin-left: 10px;"
                       :src=fileFunction.getViewFilePathByUserFileId(pic)
              />
            </div>
          </n-scrollbar>
        </n-space>
        <n-button block type="primary" size="large"
                  :loading="buttonLoading" style="margin-top: 20px" @click="paintTest" v-if="isResourceOk">生成
        </n-button>
      </n-grid-item>
      <n-grid-item span="8">
        <n-form :model="storyPaintConfig" :rules="rules" ref="formRef3">
          <n-form-item label="随机种子" path="defaultSeed">
            <n-input-number v-model:value="storyPaintConfig.defaultSeed"/>
            <n-button style="margin-left: 5px;" type="primary">随机</n-button>
          </n-form-item>
          <n-row>
            <n-col :span="11">
              <n-form-item label="采样器" path="defaultSamplerName">
                <n-select
                    :options="samperOptions" v-model:value="storyPaintConfig.defaultSamplerName" placeholder="请选择"
                />
              </n-form-item>
            </n-col>
            <n-col :span="1"></n-col>
            <n-col :span="11">
              <n-form-item label="提示词相关度" path="defaultCfgScale">
                <n-input-number v-model:value="storyPaintConfig.defaultCfgScale"/>
              </n-form-item>
            </n-col>
          </n-row>

          <n-row>
            <n-col :span="11">
              <n-form-item label="批次数量" path="defaultBatchSize">
                <n-input-number v-model:value="storyPaintConfig.defaultBatchSize"/>
              </n-form-item>
            </n-col>
            <n-col :span="1"></n-col>
            <n-col :span="11">
              <n-form-item label="采样迭代步数" path="defaultSteps">
                <n-input-number v-model:value="storyPaintConfig.defaultSteps"/>
              </n-form-item>
            </n-col>
          </n-row>
          <n-row>
            <n-form-item :show-label="false" path="defaultRestoreFaces">
              <n-checkbox v-model:checked="storyPaintConfig.defaultRestoreFaces">
                脸部修复
              </n-checkbox>
            </n-form-item>
            <n-form-item :show-label="false" path="defaultEnableHr">
              <n-checkbox v-model:checked="storyPaintConfig.defaultEnableHr">
                高清修复
              </n-checkbox>
            </n-form-item>
            <n-form-item :show-label="false" path="defaultEnableHr">
              <n-checkbox v-model:checked="storyPaintConfig.enableRegionalPrompter">
                regional prompter
              </n-checkbox>
            </n-form-item>
            <n-form-item :show-label="false" path="defaultEnableHr">
              <n-checkbox v-model:checked="storyPaintConfig.enableOpenPose">
                OpenPose
              </n-checkbox>
            </n-form-item>

            <n-form-item :show-label="false" path="defaultEnableHr">
              <n-checkbox v-model:checked="storyPaintConfig.enableReference">
                Reference
              </n-checkbox>
            </n-form-item>
          </n-row>
          <n-card v-if="storyPaintConfig.defaultEnableHr" :content-style="{padding: '10px 10px 10px 10px'}">
            <n-row>
              <n-col :span="13">
                <n-form-item v-if="storyPaintConfig.defaultEnableHr" label="方法算法" path="defaultHrUpscaler">
                  <n-select v-model:value="storyPaintConfig.defaultHrUpscaler" :options="hrUpscalerOptions">
                  </n-select>
                </n-form-item>
              </n-col>
              <n-col :span="1"></n-col>
              <n-col :span="10">
                <n-form-item v-if="storyPaintConfig.defaultEnableHr" label="采样步数" path="defaultHrSecondPassSteps">
                  <n-input-number v-model:value="storyPaintConfig.defaultHrSecondPassSteps" :min="1" :max="150"
                                  :step="1"/>
                </n-form-item>
              </n-col>
            </n-row>
            <n-row>
              <n-col :span="13">
                <n-form-item v-if="storyPaintConfig.defaultEnableHr" label="重绘幅度" path="defaultDenoisingStrength">
                  <n-input-number v-model:value="storyPaintConfig.defaultDenoisingStrength" :min="0" :max="1.0"
                                  :step="0.01"/>
                </n-form-item>
              </n-col>
              <n-col :span="1"></n-col>
              <n-col :span="10">
                <n-form-item v-if="storyPaintConfig.defaultEnableHr" label="放大倍率" path="defaultHrScale">
                  <n-input-number v-model:value="storyPaintConfig.defaultHrScale" :min="1" :max="4" :step="0.05"/>
                </n-form-item>
              </n-col>
            </n-row>
          </n-card>
        </n-form>
      </n-grid-item>
    </n-grid>
    <n-tabs type="line" animated>
      <n-tab-pane name="Lora" tab="Lora模型" display-directive="show">
        <n-grid x-gap="12" :cols="8">
          <n-gi v-if="loraList.length >0" v-for="lora in loraList">
            <n-card>
              <template #cover>
                <div class="image-container-lora" @contextmenu="handleContextMenu('lora', lora,$event)">
                  <n-image :src="fileFunction.getSDModelImage(lora.modelName,'lora')" class="centered-image-lora"/>
                </div>
              </template>
              <n-ellipsis style="width: 100%">
                {{ lora.modelName }}
              </n-ellipsis>
            </n-card>
          </n-gi>
        </n-grid>
      </n-tab-pane>
      <n-tab-pane name="T.I Embedding" tab="T.I Embedding" display-directive="show">
        <n-grid x-gap="12" :cols="8">
          <n-gi v-if="embeddingList.length >0" v-for="embedding in embeddingList">
            <n-card>
              <template #cover>
                <div class="image-container-lora"
                     @contextmenu="handleContextMenu('embedding' ,embedding, $event)">
                  <n-image :src="embedding.modelImage" class="centered-image-lora"/>
                </div>
              </template>
              <n-ellipsis style="width: 100%">
                {{ embedding.modelName }}
              </n-ellipsis>
            </n-card>
          </n-gi>
        </n-grid>
      </n-tab-pane>
    </n-tabs>
    <n-dropdown
        placement="bottom-start"
        trigger="manual"
        :x="x"
        :y="y"
        :options="loraSelectModelOption"
        :show="showDropdown"
        :on-clickoutside="clickOutSide"
        @select="handleSelect"
    />
  </div>
</template>

<script setup lang="ts">
import {nextTick, reactive, ref} from 'vue'
import {useMessage} from 'naive-ui'
import {resourceController} from '@/api/resource/resourceController.api'
import {sdControllerApi} from "@/api/sd/sdController.api"
// @ts-ignore
import fileFunction from "@/libs/globalFunction/file"
import {stableDiffusionDict} from '@/dictionary/stableDiffusionDict'
import {paintArtEnums} from "@/enums/paintArtEnums";

// 加载资源配置Loading
const resourceLoading = ref(false)
const selectedResource = ref("")
const modelOption = ref([])
const vaeOption = ref([])
const loraList = ref([])
const formRef1 = ref(null)
const formRef2 = ref(null)
const formRef3 = ref(null)
const message = useMessage();
const imageSrc = ref("")
const taskStatus = ref("waiting");
const buttonLoading = ref(false)
const picList = ref([])
const embeddingList = ref([])
// 默认sizeOption
const showDropdown = ref(false)
const x = ref(0)
const y = ref(0)
const selectModel = ref(null)
const samperOptions = ref([])
const isResourceOk = ref(true)
const {imageSizeOption, hrUpscalerOptions,loraSelectModelOption} = stableDiffusionDict()

const rules = {
  sizeOption: {
    required: true,
    message: '请选择尺寸信息'
  },
  defaultModel: {
    required: true,
    message: '请选择模型'
  },
  defaultVae: {
    required: true,
    message: '请选择模型'
  },
  defaultSeed:{
    required: true,
    message: '请输入随机种子'
  },
  defaultSamplerName: {
    required: true,
    message: '请选择采样器'
  },
  defaultBatchSize: {
    required: true,
    message: '请输入批次数量'
  },
  defaultSteps: {
    required: true,
    message: '请输入采样迭代步数'
  }
};

const demoPrompt = ref("1 girl")
const storyPaintConfig = ref(reactive({
  resourceId: '',
  defaultStyleId: '',
  defaultPrompt: '(masterpiece:1.4, best quality),unity 8k wallpaper,ultra detailed,beautiful and aesthetic,perfect lighting,detailed background,',
  defaultNegativePrompt: 'NSFW,paintings, sketches, lowres, normal quality, worst quality, low quality, cropped, dot, mole, ugly, grayscale, monochrome, duplicate, morbid, mutilated, missing fingers, extra fingers, too many fingers, fused fingers, mutated hands, bad hands, poorly drawn hands, poorly drawn face, poorly drawn eyebrows, bad anatomy, cloned face, long neck, extra legs, extra arms, missing arms missing legs, malformed limbs, deformed, simple background, bad proportions, disfigured, skin spots, skin blemishes, age spot, bad feet, error, text, extra digit, fewer digits, jpeg artifacts, signature, username, blurry, watermark, mask, logo',
  defaultSeed: -1,
  defaultSamplerName: 'DPM++ 2M',
  defaultBatchSize: 1,
  defaultSteps: 20,
  defaultWidth: 512,
  defaultHeight: 512,
  defaultCfgScale: 7,
  defaultModel: '',
  defaultVae: '',
  sizeOption: '1',
  defaultVoiceName: '',
  defaultClipSkip: 2,
  defaultVolume: 1.0,
  defaultRate: 1.0,
  defaultPitch: 1.0,
  voiceType: 'free',
  defaultRestoreFaces: false,
  defaultEnableHr: false,
  defaultHrUpscaler: 'R-ESRGAN 4x+',
  defaultHrScale: 2,
  defaultDenoisingStrength: 0.50,
  defaultHrSecondPassSteps: 8,
  enableRegionalPrompter: false,
  enableOpenPose: false,
  enableReference: false
}))

const getResourceConfig = (resouceId: string) => {
  resourceLoading.value = true
  resourceController.getReousceConfig(resouceId)
      .then(res => {
        isResourceOk.value = true
        resourceLoading.value = false
        if (res.code === 200) {
          modelOption.value = []
          vaeOption.value = []
          loraList.value = []
          res.data.modelList.forEach((item: any) => {
            // @ts-ignore
            modelOption.value.push({
              label: item.fileName,
              value: item.fileName,
              url: item.modelImage
            })
          })
          res.data.vaeList.forEach((item: any) => {
            // @ts-ignore
            vaeOption.value.push({
              label: item,
              value: item
            })
          })

          res.data.samplerList.forEach((item: any) => {
            // @ts-ignore
            samperOptions.value.push({
              label: item,
              value: item
            })
          })
          loraList.value = res.data.loraList
          embeddingList.value = res.data.embeddingList
          storyPaintConfig.value.defaultModel = res.data.selectModel
          storyPaintConfig.value.defaultVae = res.data.selectVae
        } else {
          isResourceOk.value = false
        }
      }).catch(()=>{
        isResourceOk.value = false
      }).finally(() => {
        resourceLoading.value = false
      })
}


// 选中资源后
const resourceSelect = (value: string) => {
  selectedResource.value = value
  getResourceConfig(value)
}


const changeModel = (value: string) => {
  // resourceController.cha
  const data = {
    resourceId: selectedResource.value,
    key: 'sd_model_checkpoint',
    value: value
  }
  changeOption(data)
}

const changeVae = (value: string) => {
  const data = {
    resourceId: selectedResource.value,
    key: 'sd_vae',
    value: value
  }
  changeOption(data)
}

const changeOption = (data: any) => {
  resourceLoading.value = true
  resourceController.changeOption(data).then(res => {
    if (res.code != 200) {
      message.error(res.msg);
    }
  }).catch(() => {
    message.error("切换参数失败！")
  }).finally(() => {
    resourceLoading.value = false
  })
}

const setSizeOption = (sizeOption: any) => {
  if (sizeOption === "1") {
    storyPaintConfig.value.defaultWidth = 512
    storyPaintConfig.value.defaultHeight = 512
  } else if (sizeOption === "2") {
    storyPaintConfig.value.defaultWidth = 768
    storyPaintConfig.value.defaultHeight = 512
  } else if (sizeOption === "3") {
    storyPaintConfig.value.defaultWidth = 512
    storyPaintConfig.value.defaultHeight = 768
  } else if (sizeOption === "4") {
    storyPaintConfig.value.defaultWidth = 1024
    storyPaintConfig.value.defaultHeight = 768
  } else if (sizeOption === "5") {
    storyPaintConfig.value.defaultWidth = 768
    storyPaintConfig.value.defaultHeight = 1024
  }
}


const loadConfig = (paintConfig: string, selectedResource: string) => {
  // 当前的资源是否为已选中的资源
  resourceSelect(selectedResource)
  storyPaintConfig.value.resourceId = selectedResource
  if (paintConfig != null && paintConfig.length > 0) {
    const config = JSON.parse(paintConfig)
    storyPaintConfig.value.defaultPrompt = config.defaultPrompt
    storyPaintConfig.value.defaultHeight = config.defaultHeight
    storyPaintConfig.value.defaultWidth = config.defaultWidth
    storyPaintConfig.value.defaultSeed = config.defaultSeed
    storyPaintConfig.value.defaultSamplerName = config.defaultSamplerName
    storyPaintConfig.value.defaultBatchSize = config.defaultBatchSize
    storyPaintConfig.value.defaultSteps = config.defaultSteps
    storyPaintConfig.value.defaultCfgScale = config.defaultCfgScale
    storyPaintConfig.value.defaultRestoreFaces = config.defaultRestoreFaces
    storyPaintConfig.value.defaultEnableHr = config.defaultEnableHr
    storyPaintConfig.value.defaultHrUpscaler = config.defaultHrUpscaler
    storyPaintConfig.value.defaultHrScale = config.defaultHrScale
    storyPaintConfig.value.defaultDenoisingStrength = config.defaultDenoisingStrength
    storyPaintConfig.value.defaultHrSecondPassSteps = config.defaultHrSecondPassSteps
    storyPaintConfig.value.defaultStyleId = config.defaultStyleId
    storyPaintConfig.value.defaultNegativePrompt = config.defaultNegativePrompt
    storyPaintConfig.value.defaultModel = config.defaultModel
    storyPaintConfig.value.defaultVae = config.defaultVae
    storyPaintConfig.value.sizeOption = config.sizeOption
    storyPaintConfig.value.enableReference = config.enableReference
    storyPaintConfig.value.enableOpenPose = config.enableOpenPose
    storyPaintConfig.value.enableRegionalPrompter = config.enableRegionalPrompter
  }
}

const getConfig = async () => {
  return Promise.all([
    validateForm(formRef1),
    validateForm(formRef2),
    validateForm(formRef3)
  ]).then(async (results) => {
    const allValid = results.every((valid) => valid)
    if (allValid) {
      return {...storyPaintConfig.value}
    }else {
      return null
    }
  })
}

defineExpose({
  getConfig, loadConfig
})


const paintTest = async () => {
  Promise.all([
    validateForm(formRef1),
    validateForm(formRef2),
    validateForm(formRef3)
  ]).then(async (results) => {
    const allValid = results.every((valid) => valid)
    if (allValid) {
      const sdTxt2ImgPrompt: SD.Txt2ImgInput = reactive({
        prompt: '',
        negativePrompt: '',
        seed: -1,
        samplerName: 'DPM++ 2M',
        batchSize: 1,
        steps: 20,
        width: 512,
        height: 512,
        promptEn: '',
        cfgScale: 7,
        savePath: '',
        restoreFaces: false,
        enableHr: false,
        hrUpscaler: 'R-ESRGAN 4x+',
        hrScale: 2.0,
        denoisingStrength: 0.5,
        hrSecondPassSteps: 8
      });
      sdTxt2ImgPrompt.prompt = demoPrompt.value + "," + storyPaintConfig.value.defaultPrompt;
      sdTxt2ImgPrompt.negativePrompt = storyPaintConfig.value.defaultNegativePrompt;
      sdTxt2ImgPrompt.seed = storyPaintConfig.value.defaultSeed;
      sdTxt2ImgPrompt.samplerName = storyPaintConfig.value.defaultSamplerName;
      sdTxt2ImgPrompt.batchSize = storyPaintConfig.value.defaultBatchSize;
      sdTxt2ImgPrompt.steps = storyPaintConfig.value.defaultSteps;
      sdTxt2ImgPrompt.sdResourceId = storyPaintConfig.value.resourceId
      // 脸部修复
      if (storyPaintConfig.value.defaultRestoreFaces) {
        sdTxt2ImgPrompt.restoreFaces = storyPaintConfig.value.defaultRestoreFaces
      }
      // 高清修复
      if (storyPaintConfig.value.defaultEnableHr) {
        sdTxt2ImgPrompt.enableHr = storyPaintConfig.value.defaultEnableHr
        sdTxt2ImgPrompt.hrUpscaler = storyPaintConfig.value.defaultHrUpscaler
        sdTxt2ImgPrompt.hrScale = storyPaintConfig.value.defaultHrScale
        sdTxt2ImgPrompt.hrSecondPassSteps = storyPaintConfig.value.defaultHrSecondPassSteps
        sdTxt2ImgPrompt.denoisingStrength = storyPaintConfig.value.defaultDenoisingStrength
      }
      sdTxt2ImgPrompt.cfgScale = storyPaintConfig.value.defaultCfgScale;
      sdTxt2ImgPrompt.width = storyPaintConfig.value.defaultWidth;
      sdTxt2ImgPrompt.height = storyPaintConfig.value.defaultHeight;
      buttonLoading.value = true;
      try {
        const data = await sdControllerApi.generateText2ImgAndCheck(sdTxt2ImgPrompt, 1000 * 120)
        const images = data.userFileId.split(',')
        for (let i = 0; i < images.length; i++) {
          picList.value.push(images[i])
        }
        imageSrc.value = images[0]
      } catch (e) {
        message.error("出错啦！")
      } finally {
        buttonLoading.value = false
      }
    }
  })
}


const clickOutSide = () => {
  showDropdown.value = false
}


const handleSelect = (key: string | number) => {
  showDropdown.value = false
  if (key === 'addPositive') {
    storyPaintConfig.value.defaultPrompt = storyPaintConfig.value.defaultPrompt + "," + selectModel.value.modelName
  } else if (key === 'addNegative') {
    storyPaintConfig.value.defaultNegativePrompt = storyPaintConfig.value.defaultNegativePrompt + "," + selectModel.value.modelName
  }
}


const handleContextMenu = (type: string, model: any, e: MouseEvent) => {
  e.preventDefault()
  selectModel.value = Object.assign({}, model);
  showDropdown.value = false
  if (type === 'lora') {
    selectModel.value.modelName = '<lora:' + model.modelName + ':1>'
  }
  nextTick().then(() => {
    showDropdown.value = true
    x.value = e.clientX
    y.value = e.clientY
  })
}


const validateForm = (fr:any) =>{
  return new Promise((resolve) => {
    fr.value?.validate((errors) => {
      if(!errors){
        resolve(true)
      }else {
        resolve(false)
      }
    });
  });
}


</script>
<style>

.image-container {
  height: 300px; /* 设置容器的固定高度 */
  display: flex; /* 使用Flex布局 */
  align-items: center; /* 垂直居中 */
  justify-content: center; /* 水平居中 */
}

.centered-image {
  max-height: 100%; /* 图片的最大高度为容器高度，宽度会自动适应宽高比例 */
  max-width: 100%; /* 图片的最大宽度为容器宽度，高度会自动适应宽高比例 */
}

.image-container-lora {
  height: 150px; /* 设置容器的固定高度 */
  display: flex; /* 使用Flex布局 */
  align-items: center; /* 垂直居中 */
  justify-content: center; /* 水平居中 */
}

.centered-image-lora {
  max-height: 100%; /* 图片的最大高度为容器高度，宽度会自动适应宽高比例 */
  max-width: 100%; /* 图片的最大宽度为容器宽度，高度会自动适应宽高比例 */
}

.selected {
  border: 3px solid #8250df;
}

</style>
